using System;
using System.Collections.Generic;
using System.Linq;

namespace Sustainalytics.ESGRatings.BusinessLogic.Computing
{
    public class IndexByKey<TU, TV> : IIndex<TU, TV>
    {
        private readonly Dictionary<TU, int> _directIndex = new Dictionary<TU, int>();
        private readonly Dictionary<TV, int> _selectorIndex = new Dictionary<TV, int>();
        private readonly Dictionary<TU, int> _itemInternalGrouping = new Dictionary<TU, int>();
        private readonly Dictionary<int, List<TU>> _selectorKeysDictionary = new Dictionary<int, List<TU>>();

        //private readonly Dictionary<TU, int> _directIndex = new Dictionary<TU, int>(); //stores GUID of companies and region internal Id
        //private readonly Dictionary<TV, int> _selectorIndex = new Dictionary<TV, int>();

        //private readonly Dictionary<TU, TV> _keySelectorList = new Dictionary<TU, TV>();
        //private readonly List<TU> _reverseIndex = new List<TU>();

        private IndexByKey()
        {
        }

        public int Count
        {
            get { return _directIndex.Count; }
        }

        public int[] GroupByHeads { get { return new int[0]; } }

        public int SelectorsCount { get { return _selectorIndex.Count; } }

        /// <exception cref="Exception">A delegate callback throws an exception.</exception>
        public static IndexByKey<TU, TV> Create<T>(IEnumerable<T> items, Func<T, TU> keySelector,
            Func<T, TV> valueSelector)
        {
            var returnIndex = new IndexByKey<TU, TV>();
            var itemIndex = 0;
            int selectionIndex = 0;
            foreach (var item in items)
            {
                TU keyProperty = keySelector(item);
                TV selectionProperty = valueSelector(item);

                List<TU> keys = new List<TU>();

                int regionKey = -1;
                if (!returnIndex._selectorIndex.TryGetValue(selectionProperty, out regionKey))
                {
                    regionKey = selectionIndex++;
                    returnIndex._selectorIndex.Add(selectionProperty, regionKey);
                    returnIndex._selectorKeysDictionary.Add(regionKey, keys);
                }

                returnIndex._selectorKeysDictionary[regionKey].Add(keyProperty);
                returnIndex._itemInternalGrouping.Add(keyProperty, regionKey);
                returnIndex._directIndex.Add(keyProperty, itemIndex++);
            }

            return returnIndex;
        }

        public IEnumerable<TU> GetGroupElements(TU itemKey)
        {
            List<TU> itemsInGroup;
            int groupId;
            _itemInternalGrouping.TryGetValue(itemKey, out groupId);
            _selectorKeysDictionary.TryGetValue(groupId, out itemsInGroup);
            return itemsInGroup;
        }

        public IEnumerable<TU> GetGroupElements(IEnumerable<TV> itemSelector)
        {
            var returnResult = new List<TU>();
            foreach (var selector in itemSelector)
            {
                int internalSelectorIndex;
                if (!_selectorIndex.TryGetValue(selector, out internalSelectorIndex))
                {
                    throw new ArgumentOutOfRangeException("itemSelector");
                }

                returnResult.AddRange(_selectorKeysDictionary[internalSelectorIndex]);
            }
            return returnResult;
        }

        public int GetGroupId(TV itemSelector)
        {
            int groupId;
            if (!_selectorIndex.TryGetValue(itemSelector, out groupId))
            {
                groupId = -1;
            }
            return groupId;
        }

        public int GetIndex(TU key)
        {
            int internalKeyId;
            if (!_directIndex.TryGetValue(key, out internalKeyId))
            {
                internalKeyId = -1;
            }
            return internalKeyId;
        }

        public int[] GetIndex(object p)
        {
            List<int> result = new List<int>();
            try
            {
                var selector= (TV)Convert.ChangeType(p, typeof(TV));
                int groupId = GetGroupId(selector);
                if (groupId >= 0)
                {
                    List<TU> keys = _selectorKeysDictionary[groupId];
                    result.AddRange(keys.Select(key => _directIndex[key]));
                }

            }
            catch (InvalidCastException )
            {
                return new int[0];
            }
            return result.ToArray();
        }

        public int[] GetIndex()
        {
            return _directIndex.Values.ToArray();
        }

        public TU GetItem(int index)
        {
            if (index >= _directIndex.Count)
            {
                throw new IndexOutOfRangeException("index");
            }
            return _directIndex.Keys.ElementAt(index);
        }

        public TU[] GetReverseIndexCopy()
        {

            return _directIndex.Keys.ToArray();
        }
    }
}